//1.什么是指针
//指针就是变量，用来存放地址的一个变量（存放在指针中的值都被当做地址处理）

//内存其实就是一个很大的空间，被划分成了很多的小格子，也就是一个内存单元每个小格子的大小为1个字节每个小格子都有一个编号，也就是地址0xFFFFFFFF之类的。
//这些数字的产生关系到电信号，想明白就去学计组

//int *p=&a，用来存放a地址的变量p就被称为指针变量

//对于32位的机器，假设有32根地址线每根地址线在寻址时候产生一个的是一个电信号正电/负电（1/0)那么32根地址线产生的地址就是0000....00共32个0到111..111
//32个1之间。这就是地址产生的由来   》》》所以一个指针变量的大小为4字节，一个64位机器指针变量大小为8字节   4x8=32   8x8=64

//两个16进制位占用1个字节ab           -----这个两位就相当于十进制的个位十位两位例如18

//32位下共有2~32次方个地址。也就是字节      64位有2~64次方个地址，也就是字节;










//2.指针和指针类型

//void main()
//{
//	int a = 0x11223344;
//	int* pa = &a;
//  
//	*pa = 0;//*是解引用操作符，能查看所保存地址中的变量
//	printf("%d", sizeof(char*));
//	printf("%d", sizeof(short*));
//	printf("%d", sizeof(int*));
//}
//int a=0x11223344; char *p=&a;如果想用char类型的指针存放a的地址也能存放。但是*p=0；运用解引用操作符对a进行重新赋值只能操作一个字节，操作的这个字节也是低地址存放的那个。
//然后a的值变成了0x11223300,也就是仅仅将一个字节复赋值进去其余位没有改变。定义的是什么类型的指针那么。这个类型的变量占用了多少变量，这个指针
//进行解引用赋值的时候就能只能操作几个字节。

//int *p,p+1此时地址跳过了4个字节，因为是int类型的    char*p,p+1,此时p的地址跳过了1个字节，因为char类型的变量占用一个字节
//指针类型决定了指针的步长，走一步走多远

//总结：指针的类型决定了，对指针解引用的时候有多大的权限（能够操作几个字节）。比如char*的指针解引用就只能访问一个自己，而int*的指针解引用就能
//访问4个字节











//3.野指针

//野指针是指向未分配或者已经释放的内存地址
//就是指针指向的位置是不可知的（随机的，不正确的，没有明确限制的）
// 全局变量中定义的指针不进行初始化是空指针                
// 请记住，全局变量中的指针不初始化默认为空指针，不是野指针。局部变量中的指针不初始化指向未知，是野指针
// 函数内部不管是主函数还是自定义函数定义的指针不进行初始化地址是随机的。这就是野指针的一种情况              -----局部变量的指针不进行初始化都是野指针
//void main()
//{
//	//int arr[10] = { 0 };
//	//int *p=arr;//数组名-首元素的地址，两种情况除外1.sizeof(数组名)  &数组名    此时的数组名代表着整个地址
//	int a;//局部变量不初始化默认是随机值
//	int* p;//没有进行初始化的指针叫野指针，现在指向的地址是随机的
//	*p = 20;//此时的20内存地址不固定，虽然能存进去，但是就怕指向的地址保存着重要的数据，更改了就可能造成重大的错误
//}

//指针的越界访问也会导致野指针         也就是指向了尚未定义的地址
//void mian()
//{
//	int arr[10] = { 0 };
//	int* p = arr;
//	int i = 0;
//	for (i = 0; i < 12; i++)
//	{
//		*(p++) = i;//也就是数组就是个位置，当超过了10个，以外的就是野指针
//	}
//}


//指针指向的空间释放
//int* test()
//{
//	int a = 10;
//	return &a;//只要是返回的是临时变量的地址都会出现问题
//}
//void main()
//{
//	int* p = test();//局部变量的使用后连带这内存空间一起被销毁，但是在同一时间这个空间可能已经被占用了；会出现错误。
//}

//避免野指针的出现
//指针初始化    小心指针越界   指针指向空间释放即及时置NULL   指针使用之前检查有效性

//#include <cstddef>//对指针赋值为空需要调用的空函数        空指针不能直接解引用进行赋值，但是可以赋予新的地址  int *a=NULL;a=&b; 
//void main()
//{
//	int* p = NULL;//不知道赋值为多少就这样先设为空就不会造成野指针的情况，但是需要调用库函数  NULL的实际意义就是（void*）0>>>这种类型
//设置了NULL的指针并不能进行赋值，但是可以设置很多NULL,等真正需要这个指针指向地址的时候就可以使用了
//}

//#include<stddef.h>
//void mian()
//{
//	int a = 10;
//	int* p = &a;
//	p = NULL;//
//	*p = 20;//在p已经初始化为NULL的时候，*p就不能够再去赋值了
//}

//指针有效性     也就是虽然是指针，但是空指针并不能使用。不是空指针才能使用










//4.指针运算	
//指针+-整数    指针-指针    指针的关系运算

//void main()//指针加减整数的例子,浮点类型的也可以
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int i = 0;
//	int sz = sizeof(arr)/sizeof(arr[0]);
//	int* p = arr;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", *p);
//		p += 1;//只要不越界+-都可以     -----指针的类型决定了指针的操作步长，int类型的+1就是跳过4个字节，指向下个变量的地址
//	}
//}                    *vp++=0;这个表达式的意思是先  *vp=0,然后地址++，在循环里面就是下一个再进行+1
//                                  换成普通变量就是    a++=0;也是先赋值，在进行加一

//void main()//指针相减的实际意义
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	pirntf("%d", arr[9] - arr[0]);//这就是指针减去指针，在同一个数组内就是下标数字相见相减。本质上是地址上相减。但是如果是不同的数组相减，得到的
//	//结果可能就是随机数，翻过来相减就是负数      最末尾减去首位的元素的下标+1才是元素的个数           -----地址相减本质上还是两组数字进行相减
//	//strlen()函数，求字符串长度的函数
//}



//my_strlen(char* str)//以指针-指针的形式实现strlen计数的方式
// 我们要清楚这个函数的停止的方式要么是字符串中的\0,要么是数组中的0，这个0在字符数组中必须是这样设置的{'a','b',0,'c'},此时的0和'\0'是等价的
//{
//	char* start = str;
//	char* end = str;
//	while (*end != '\0')
//	{
//		end++;
//	}
//	return end - start;
//}
//void main()
//{
//	char arr[] = "bit";//注意初始化数组的时候""里面的字符串都会有个字符\0并且占用一个元素位，空间大小和元素的相同
//	int len = my_strlen(arr);
//	printf("%d", len);
//}

//指针的关系运算    可以通过这种形式通过地址对数组进行赋值

//#define five 5
//void main()
//{
//	float values[five];
//	float* vp;//这种就野指针，地址是随机的，但是能用
//	for (vp = &values[five]; vp > &values[0];/*运行完下面的东西后什么也不执行，别vp=0,然后在这里进行--，虽然能编译但是不合标准*/)
//	{
//		*--vp = 0;//
//		//*vp=0
//	}
//}
//标准规定：允许指向数组元素的指针与指向数组的最后一个元素的那个内存位置的指针比较，但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
//也就是说数组内的元素想比较，可以与最后一个元素后面的一个越界位置相比较，但是不能和首元素前面的的一个越界位置相比较

//补充：同类型的指针相减得到的结果是之间的元素个数（地址差值/字节数    换一种方法叫做指针的偏移量）       
//当指针指向的类型是不同的，比如一个int，一个char。如果两者相减（字节数/int类型字节），得到的就是这个结果










//5.指针和数组
//数组名就是首元素的地址，但是两种情况除外   sizeof和&数组名这两种情况表示的是整个数组
//void main()
//{
//	int arr[10];
//	printf("%p\n", &arr[0]+1);
//	printf("%p\n", arr+1);
//	printf("%p\n", &arr+1);//这种情况是将整个数组跳了过去
//}


//6.二级指针
//存放指针变量地址的指针就是二级指针          多级指针的用处也就是用来存放上一级的指针的地址
//void main()
//{
//	int a = 10;
//	int* pa = &a;//int*现在就是一级类型   pa现在就是一级指针类型
//	int** ppa=&pa;//这个ppa就是指针变量pa的二级指针变量       其类型就是int**
//	int*** pppa = &ppa;//pppa就是三级指针变量     其类型就是int***
//
//	printf("%d\n", *pppa);//这样打印出来的是二级指针变量ppa的地址
//	printf("%d\n", **pppa);//这样打印的是一级指针变量pa的地址
//	printf("%d\n", ***pppa);//这样打印出来的是变量a的值10
//}
//二级指针接受的是一级指针的地址，并不是接收的一级指针的变量

//7.指针数组以及数组指针    指针数组的本质>>>存放指针的数组        数组指针的本质>>>指向一个数组的指针

//void main()
//{
//	int a = 1;
//	int b = 2;
//	int c = 3;
//	int* arr[3] = { &a,&b,&c };//这就是指针数组      -----指针数组是数组 -----存放着指针的数组
//	int i = 0;
//	for (i = 0; i < 3; i++)
//	{
//		printf("%d\n", *arr[i]);//可以这样进行解引用
//	}
//

// int main()
// {
//     int arr[]={1,2,3,4,5};
//     short *p=(short*)arr;//数组进行强制类型转换的时候，是指针类型的转换. 还有一点强制类型转换并不会改变变量字节的大小 int a；a=（short）a，a依然是4个字节
//    //补充一点，强制类型转换如 int a,  char b=(char)a;   其规则是先将a以int的形式拿出来，再以char类型的存储方式获取相应的自字节，再存入b中
//     int i=0;
//     for(i=0;i<4;i++)
//     {
//         *(p+i)=0;//short*类型的数组一次只能操作两个字节，所以并且这样的操作是连续的，把int这个字节操作两次才会进行下一个字节
//     }
//     printf("%d",arr[0]);
// return 0;
// }

//地址中存放16进制的数字是倒着存放，0x11223344>>>0x44332211.改变字节的时候从44先开始 16进制两个位数是一个字节（小端的方式）





//被const修饰的变量的含义
//补充：被const修饰的常属性变量，还是能通过地址将变量的值进行修改。除非将指针变量也就行常属性的修饰
//但是用const修饰的有两种写法，代表着两种意思
//（1）const int* p;const放在变量*左边时，修饰的是*p。也就是说，不能通过p来改变*p（变量）的值         -----地址指向的值是不能改变的
//（2）int* const p;这个const是用来修饰p的，也就是说p只能用来存放一个变量的地址，并不能在用作存放其他地址的指针了。但是能通过*p修改原有变量的值              -----这个指针只能保存当变变量的地址，不能再进行变更了
//#include<stdio.h>
//int main(void)
//{
//    int a=5;
//    int b=6;
//
//    int* const pa=&a;//这个意思也就是指向的地址是固定的，不能变更了，但是地址里面的变量可以更改
////    pa=&b;    //无法编译，因为地址已经无法更改
//    *pa=55;
//    printf("%d\n",a);
//
//    const int *pb=&b;//这个的意思是*p也就是，指针指向的变量的值已经固定，无法更改，但是指针的指向可以更改
//    pb=&a;//可以更换指针的指向
//    printf("%d\n%d",a,b);
//}

//补充：指针指向的就是变量所占字节地址最小的那个字节

//补充：指针的类型决定了指针进行+，-，还有解引用的步长

//补充：数组或者结构体的指针进行+-操作时，+-1的步长就是其总数组或者结构体大小的字节数        -----也就是指向数组的指针和指向结构体的指针

//指针在32位平台上是4个字节，在64位平台上是8个字节！！！！！



//补充：将指针当做形参进行传递的时候，如果函数用的是同级别指针接收，那么这个函数的形参虽然保存了这个地址，但是，这个形参指针的地址和实参指针的地址并不是同一个
//如代码所示：

//#include<stdio.h>
//void test(int *pp)//如果用一级指针进行接收，那么这个pp解引用也只是能看见a变量的地址。   但是一级指针不能够多次进行解引用
//{
//    printf("%d",*pp);
////    printf("%d",**pp);一级指针并不能进行两次解引用
//
//}
//
//int main(void)
//{
//    int a=5;
//    int *p=&a;
//    printf("%d\n",&a);
//
//    test(&p);
//}



//#include<stdio.h>
//
//void test(int *x)//这就是用同级指针接收同级指针的情况
////实参是指针的情况下，用指针接收，这个形参指针变量是重新复制出来的，接收的值确实是原来指针保存的变量的地址
////这里的形参指针能够让实参指针保存的变量的值发生改变，但是却不能改变实参指向的地址
//{
//    printf("形参指针变量x的地址是%p\n",&x);          -----此处只是接收了a的地址，接受这个地址的是其他的指针。所以可以通过这个地址改变p变量保存的数字，但是不能改变p变量的指向
//    printf("形参变量x接受了什么地址%p\n",x);
//}
//int main(void)
//{
//    int a=9;
//    int *p=&a;
//    printf("实参变量p地址是%p\n",&p);
//    printf("实参变量p保存的地址是%p\n",p);
//    test(p);
//    return 0;
//}